package com.sigmatrix.h5.blob;




import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.microsoft.azure.storage.AccessCondition;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.blob.BlobProperties;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy;

import so.sao.file.datastore.exception.FileDatastoreException;
import so.sao.file.datastore.service.FileService;

/**
 * @ClassName: AzureCloudFileService
 */
public class AzureCloudFileService implements FileService {

    private static final String SLASH = "/";

    @Autowired
    private Map<String, CloudBlobContainer> blobContainerMap;

    /**
     * 
     * @Title: upload
     * @param containerName
     * @param data
     * @param filePath
     * @param fileType
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#upload(java.lang.String,
     *      byte[], java.lang.String, java.lang.String)
     */
    @Override
    public void upload(String containerName, byte[] data, String filePath, String fileType) throws FileDatastoreException {
        filePath = validatePath(containerName, filePath);
        if (ArrayUtils.isEmpty(data)) {
            throw new FileDatastoreException("参数不正确，不允许为空");
        }
        try {
            CloudBlockBlob blob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            blob.deleteIfExists();
            setBlobProperties(blob, fileType);
            blob.uploadFromByteArray(data, 0, data.length);
        } catch (URISyntaxException | StorageException | IOException e) {
            throw new FileDatastoreException(
                    "catch exception when call upload(String containerName, byte[] data, String filePath, String fileType) method.",
                    e);
        }
    }

    /**
     * @Title: upload
     * @param containerName
     * @param data
     * @param filePath
     * @param fileType
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#upload(java.lang.String,
     *      java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void upload(String containerName, String data, String filePath, String fileType) throws FileDatastoreException {
        upload(containerName, data.getBytes(), filePath, fileType);
    }

    /**
     * @Title: upload
     * @param containerName
     * @param inputStreamStringPath
     * @param filePath
     * @param fileType
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#upload(java.lang.String,
     *      java.io.InputStream, java.lang.String, java.lang.String)
     */
    @Override
    public void upload(String containerName, InputStream inputStream, String filePath, String fileType)
            throws FileDatastoreException {
        filePath = validatePath(containerName, filePath);
        if (inputStream == null) {
            throw new FileDatastoreException("参数不正确，不允许为空");
        }
        try {
            CloudBlockBlob blob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            blob.deleteIfExists();
            setBlobProperties(blob, fileType);
            AccessCondition condition = AccessCondition.generateIfNoneMatchCondition("*");
            blob.upload(inputStream, -1, condition, null, null);
        } catch (URISyntaxException | StorageException | IOException e) {
            throw new FileDatastoreException(
                    "catch exception when call upload(String containerName, InputStream inputStream, String filePath, String fileType) method.",
                    e);
        }

    }

    private void setBlobProperties(CloudBlockBlob blob, String contentType) {
        BlobProperties properties = blob.getProperties();
        if (StringUtils.isNotEmpty(contentType)) {
            properties.setContentType(contentType);
        }
        properties.setContentMD5(null);
        properties.setCacheControl("public, max-age=86400");
    }

    /**
     * @Title: uploadBase64
     * @param containerName
     * @param base64Data
     * @param filePath
     * @param fileType
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#uploadBase64(java.lang.String,
     *      java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void uploadBase64(String containerName, String base64Data, String filePath, String fileType)
            throws FileDatastoreException {
        upload(containerName, Base64.decodeBase64(base64Data), filePath, fileType);

    }

    /**
     * @Title: downFile
     * @param containerName
     * @param filePath
     * @return
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#downFile(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public InputStream downFile(String containerName, String filePath) throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            if (!cloudBlockBlob.exists()) {
                throw new FileDatastoreException(
                        String.format("method in downFile() , 要下载的文件%s在指定的%s容器中不存在。", filePath, containerName));
            }

            return cloudBlockBlob.openInputStream();

        } catch (URISyntaxException | StorageException e) {
            throw new FileDatastoreException("catch exception when call downFile(String containerName, String filePath) method.",
                    e);
        }
    }

    /**
     * @Title: downFileToLocal
     * @param containerName
     * @param filePath
     * @param downLocalUrl
     * @param replace
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#downFileToLocal(java.lang.String,
     *      java.lang.String, java.lang.String, boolean)
     */
    @Override
    public void downFileToLocal(String containerName, String filePath, String downLocalUrl, boolean replace)
            throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            if (!cloudBlockBlob.exists()) {
                throw new FileDatastoreException(
                        String.format("method in downFileToLocal(), 要下载的文件%s在指定的%s容器中不存在。", filePath, containerName));
            }

            Path path = Paths.get(downLocalUrl);
            if (Files.exists(path)) {
                if (replace) {
                    Files.delete(path);
                } else {
                    // TODO 文件存在不做任何操作，后续是否需要特殊处理
                    return;
                }
            }

            path.getParent().toFile().mkdirs();
            cloudBlockBlob.downloadToFile(downLocalUrl);
        } catch (URISyntaxException | StorageException | IOException e) {
            throw new FileDatastoreException(
                    "catch exception when call downFileToLocal(String containerName, String filePath, String downLocalUrl, boolean replace) method.",
                    e);
        }

    }

    /**
     * @Title: downloadAsFile
     * @param containerName
     * @param filePath
     * @param downLocalUrl
     * @param replace
     * @return
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#downloadAsFile(java.lang.String,
     *      java.lang.String, java.lang.String, boolean)
     */
    @Override
    public Path downloadAsFile(String containerName, String filePath, String downLocalUrl, boolean replace)
            throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            if (!cloudBlockBlob.exists()) {
                throw new FileDatastoreException(
                        String.format("method in downloadAsFile(), 要下载的文件%s在指定的%s容器中不存在。", filePath, containerName));
            }
            Path path = Paths.get(downLocalUrl);
            if (Files.exists(path)) {
                if (replace) {
                    Files.delete(path);
                } else {
                    // TODO 文件存在不做任何操作，后续是否需要特殊处理
                    return path;
                }
            }
            path.getParent().toFile().mkdirs();
            cloudBlockBlob.downloadToFile(downLocalUrl);
            return path;

        } catch (URISyntaxException | StorageException | IOException e) {
            throw new FileDatastoreException(
                    "catch exception when call downloadAsFile(String containerName, String filePath, String downLocalUrl, boolean replace) method.",
                    e);
        }
    }

    /**
     * @Title: getFileContent
     * @param containerName
     * @param filePath
     * @return
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#getFileContent(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public String getFileContent(String containerName, String filePath) throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            if (!cloudBlockBlob.exists()) {
                throw new FileDatastoreException(
                        String.format("method in getFileContent(), 要读取的文件%s在指定的%s容器中不存在。", filePath, containerName));
            }

            return cloudBlockBlob.downloadText(StandardCharsets.UTF_8.name(), null, null, null);

        } catch (URISyntaxException | StorageException | IOException e) {
            throw new FileDatastoreException(
                    "catch exception when call getFileContent(String containerName, String filePath) method.", e);
        }
    }

    @Override
    public void deleteFile(String containerName, String filePath) throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            cloudBlockBlob.deleteIfExists();

        } catch (URISyntaxException | StorageException e) {
            throw new FileDatastoreException(
                    "catch exception when call deleteFile(String containerName, String filePath) method.", e);
        }
    }

    /**
     * @Title: ifExistingFile
     * @param containerName
     * @param filePath
     * @return
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#ifExistingFile(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public boolean ifExistingFile(String containerName, String filePath) throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob cloudBlockBlob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);
            return cloudBlockBlob == null ? false : cloudBlockBlob.exists();

        } catch (URISyntaxException | StorageException e) {
            throw new FileDatastoreException(
                    "catch exception when call ifExistingFile(String containerName, String filePath) method.", e);
        }
    }

    /**
     * @Title: getFileDownloadUrl
     * @param containerName
     * @param filePath
     * @param expiration
     * @return
     * @throws FileDatastoreException
     * @see so.sao.file.datastore.service.FileService#getFileDownloadUrl(java.lang.String,
     *      java.lang.String, java.lang.Integer)
     */
    @Override
    public String getFileDownloadUrl(String containerName, String filePath, Integer expiration) throws FileDatastoreException {
        try {
            filePath = validatePath(containerName, filePath);
            CloudBlockBlob blob = blobContainerMap.get(containerName).getBlockBlobReference(filePath);

            return expiration == null ? blob.getUri().toString()
                    : StringUtils.join(blob.getUri().toString(), "?", generateSignForPrivateBlob(blob, expiration));

        } catch (URISyntaxException | StorageException | InvalidKeyException e) {
            throw new FileDatastoreException(
                    "catch exception when call  getFileDownloadUrl(String containerName, String filePath, Integer expiration) method.",
                    e);
        }

    }

    private String validatePath(String containerName, String filePath) throws FileDatastoreException {
        if (StringUtils.isBlank(containerName)) {
            throw new FileDatastoreException("containerName参数不能为空。");
        }
        if (StringUtils.isBlank(filePath)) {
            throw new FileDatastoreException("filePath 参数不能为空。");
        }
        if (StringUtils.startsWith(filePath, SLASH)) {
            filePath = StringUtils.substring(filePath, 1);
        }
        return filePath;
    }

    private String generateSignForPrivateBlob(CloudBlockBlob blob, Integer expiration)
            throws InvalidKeyException, StorageException {
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissionsFromString("r");
        Date zoneTime = Date.from(ZonedDateTime.now(ZoneId.of("GMT+8")).toInstant());
        Date expireTime = DateUtils.addMinutes(zoneTime, expiration);
        policy.setSharedAccessExpiryTime(expireTime);
        return blob.generateSharedAccessSignature(policy, null);
    }
}
